package cn.touna.jss.common.util;

import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.HttpConnectionFactory;
import org.apache.http.conn.ManagedHttpClientConnection;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.DefaultHttpResponseParserFactory;
import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import org.apache.http.impl.io.DefaultHttpRequestWriterFactory;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * 基于HttpClient4.5.2版本的配置。如果需要使用长连接那么可以考虑使用该连接池，实现链接的复用。
 * 如果是短链接，该连接池则是作为一个信号量来限制总请求数。
 */
public class HttpClientFactory {

    static PoolingHttpClientConnectionManager manager = null;
    static CloseableHttpClient httpClient = null;

    public static synchronized CloseableHttpClient getHttpClient() {
        if (httpClient == null) {
            //注册访问协议相关的socket工厂
            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.INSTANCE)
                    .register("https", SSLConnectionSocketFactory.getSocketFactory())
                    .build();
            //httpConnection工厂,配置写请求/解析响应处理器
            HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory =
                    new ManagedHttpClientConnectionFactory(DefaultHttpRequestWriterFactory.INSTANCE,
                            DefaultHttpResponseParserFactory.INSTANCE);
            //DNS解析器
            DnsResolver dnsResolver = SystemDefaultDnsResolver.INSTANCE;
            //创建池化链接管理器
            manager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, connFactory, dnsResolver);
            //默认socket配置
            SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build();
            manager.setDefaultSocketConfig(socketConfig);
            //设置整个连接池的最大连接数
            manager.setMaxTotal(300);
            //设置路由的默认最大连接数，每个路由实际最大链接数默认为DefaultMaxPerRoute控制，而MaxTotal控制整个池子的最大数
            //设置过小无法支撑打并发，路由是对MaxTotal的细分
            manager.setDefaultMaxPerRoute(200);//每个路由的最大链接数
            //从池子获取连级时，连接不活跃多长时间后需要再次验证，默认为2s
            manager.setValidateAfterInactivity(5 * 1000);

            //默认请求配置
            RequestConfig defaultRequestConfig = RequestConfig.custom()
                    .setConnectTimeout(2 * 1000)//设置链接超时时间
                    .setSocketTimeout(5 * 1000)//设置等待数据超时时间，5s
                    .setConnectionRequestTimeout(2000)//设置从连接池获取链接的等待超时时间
                    .build();

            //创建HttpClient
            httpClient = HttpClients.custom()
                    .setConnectionManager(manager)
                    //链接池不是共享模式
                    .setConnectionManagerShared(false)
                    //定期回收空闲链接
                    .evictIdleConnections(60, TimeUnit.SECONDS)
                    //定期回收过期链接
                    .evictExpiredConnections()
                    //设置链接存活时间，如果不设置，则根据长连接信息决定
                    .setConnectionTimeToLive(60, TimeUnit.SECONDS)
                    //设置默认请求配置
                    .setDefaultRequestConfig(defaultRequestConfig)
                    //设置链接重用策略，即是否支持KeepAlive
                    .setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE)
                    //设置长连接，即获取长连接生产多长时间
                    .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)
                    //设置重试次数，默认是3次，当前是禁用的(根据需要开启)
                    .setRetryHandler(new DefaultHttpRequestRetryHandler(0, false))
                    .build();

            //当JVM停止或者重启，关闭连接池
            Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        httpClient.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }));
        }
        return httpClient;
    }
}
